package com.baofu.paycore.service.processor.refund;

import com.baofu.clear.service.facade.enums.EventTypeEnum;
import com.baofu.paycore.biz.external.clear.EventService;
import com.baofu.paycore.biz.external.clear.model.EventRuleResBO;
import com.baofu.paycore.common.enums.FundTypeEnum;
import com.baofu.paycore.manager.model.*;
import com.baofu.paycore.manager.storage.impl.ClearManager;
import com.baofu.paycore.service.converter.BO2BOConverter;
import com.baofu.paycore.service.facade.enums.PayToolTypeEnum;
import com.system.commons.sequence.redis.SequenceFacade;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 银行卡支付退款资金单构建处理器
 * <p>
 *     1、银行卡支付退款资金单构建
 * </p>
 * User: kwl Date: 2016/11/3 ProjectName: paycore Version: 5.0.0
 */
@Slf4j
@Service
public class RefundBankProcessor {

    /**
     * 支付单号序列成工具类
     */
    @Autowired
    private SequenceFacade sequenceFacade;

    /**
     * 充值、提现订单管理Manager
     */
    @Autowired
    private ClearManager clearManager;

    /** 会计事件服务 */
    @Autowired
    private EventService eventService;


    /**
     * 银行卡支付退款资金单构建
     *
     * <p>
     * subPayBOs：原支付银行卡资金单
     * subBOs：退款资金单资金集合
     * refundFeeAmt：本次退款，退“手续费” 总金额
     * refundAmt：业务退款总金额：本次退款，除去退款收取的“手续费”金额 和  原交易退“手续费”的剩余金额
     * bankRechargeBO：本次退款，银行卡支付充值对应 资金单
     * refundAccountAmt：本次退款，原支付单-银行卡支付资金单(payTool：BANK_CARD对应资金单)退款金额
     * </p>
     *
     * @param refundBO           支付模型
     * @param subPayBOs          原支付交易资金单
     * @param refundDirection    退款方向
     */
    public void process(RefundBO refundBO, List<SubPayBO> subPayBOs, String refundDirection) {

        log.debug("余额支付退款资金单构建参数, refundBO:{}, subPayBOs:{}",refundBO, subPayBOs);

        List<SubRefundBO> subBOs = new ArrayList<>();

        Long refundFeeAmt = refundBO.getRefundFeeAmt() - refundBO.getHasRefundFeeAmt();

        //业务退款总金额
        Long refundAmt = refundBO.getAmt() - refundBO.getHasRefundAmt() - refundBO.getFeeAmt() - refundFeeAmt;

        //银行卡充值对应资金单
        SubPayBO bankRechargeBO = null;

        //银行卡充值资金单退款金额
        Long refundBankRechargeAmt = 0L;

        for(SubPayBO subPayBO : subPayBOs) {

            //原资金单剩余可退款金额
            Long availableRefundAmt = subPayBO.getAvailableRefundAmt();
            if (0 == availableRefundAmt) {
                continue;
            }

            //本次资金单 “退款金额”
            Long payToolRefundAmt ;

            if(StringUtils.equals(FundTypeEnum.FEE.getCode(), subPayBO.getFundType())) {

                if (0 == refundFeeAmt) {
                    continue;
                }
                payToolRefundAmt = (refundFeeAmt - availableRefundAmt > 0) ? availableRefundAmt :
                        refundFeeAmt;
                refundFeeAmt -= payToolRefundAmt;
                refundBankRechargeAmt += payToolRefundAmt;

                subPayBO.setRefundTradeAmt(payToolRefundAmt);
                subPayBO.setRefundAmount(subPayBO.getRefundAmount() + payToolRefundAmt);
                refundBO.setHasRefundFeeAmt(refundBO.getHasRefundFeeAmt() + payToolRefundAmt);

                transFerRefundProcess(subPayBO, refundBO, payToolRefundAmt, subBOs);

            } else if(StringUtils.equals(FundTypeEnum.RECHARGE_TRANSFER.getCode(), subPayBO.getFundType())) {

                if (0 == refundAmt) {
                    continue;
                }
                payToolRefundAmt = (refundAmt - availableRefundAmt > 0) ? availableRefundAmt : refundAmt;
                refundAmt -= payToolRefundAmt;
                refundBankRechargeAmt += payToolRefundAmt;

                subPayBO.setRefundTradeAmt(payToolRefundAmt);
                subPayBO.setRefundAmount(subPayBO.getRefundAmount() + payToolRefundAmt);
                refundBO.setHasRefundAmt(refundBO.getHasRefundAmt() + payToolRefundAmt);

                transFerRefundProcess(subPayBO, refundBO, payToolRefundAmt, subBOs);

            } else {

                //银行卡充值对应资金单
                bankRechargeBO = subPayBO;
            }
        }

        //银行卡充值资金单，若退款方向是银行卡，则需要调用清分进行银行卡退款
        if(null != bankRechargeBO && 0 != refundBankRechargeAmt &&
                StringUtils.equals(refundDirection, bankRechargeBO.getPayToolSubType())){

            bankRechargeBO.setRefundTradeAmt(refundBankRechargeAmt);
            bankRechargeBO.setRefundAmount(bankRechargeBO.getRefundAmount() + refundBankRechargeAmt);
            bankCardRefundProcess(bankRechargeBO, refundBO, refundBankRechargeAmt, subBOs);
        }

        refundBO.getSubRefundBOMap().put(PayToolTypeEnum.BANK_CARD.getCode(), subBOs);
        refundBO.getSubRefundBOs().addAll(subBOs);
    }

    /**
     * 转账退款流程
     *
     * <p>
     * 将银行卡充值对应的转账资金单的退款单放入退款资金单集合中。并放入到集合的首位，这是考虑银行卡充值的退款，
     * 即，保证现将商户收单资金退还到用户手中.
     * </p>
     *
     * @param subPayBO            原支付单
     * @param refundBO            退款主单
     * @param payToolRefundAmt    本次该资金单（支付工具）退款金额
     * @param subRefundBOs        退款资金单集合
     */
    private void transFerRefundProcess(SubPayBO subPayBO, RefundBO refundBO, Long payToolRefundAmt,
                                       List<SubRefundBO> subRefundBOs){
        //转账资金单
        SubRefundBO refundTransferBO = subRefundBOBuild(payToolRefundAmt, subPayBO.getPayToolType());

        subRefundBOs.add(BO2BOConverter.getSubRefundBOBySubPayBO(subPayBO, refundBO, refundTransferBO));
    }

    /**
     * 银行卡退款流程
     * <p>
     * 银行卡退款流程，需根据银行卡退款配置的退款方向进行判断：
     * 1、如果退款方向是银行卡，则需要关联查询出银行卡支付过程中对应的充值流程订单信息，
     *   根据充值的银行、账户资金单，分别调用清分和账户的退款接口，进行退款
     * 2、如果退款方向是内部账户余额，只需对银行支付流程中的 转账支付进行退款即可
     * </p>
     *
     * @param subPayBO            原支付资金单细单
     * @param refundBO            退款主单
     * @param payToolRefundAmt    本次该资金单（支付工具）退款金额
     * @param subRefundBOs        退款资金单
     */
    private void bankCardRefundProcess(SubPayBO subPayBO, RefundBO refundBO, Long payToolRefundAmt,
                                       List<SubRefundBO> subRefundBOs){

        //充值原单信息查询
        RechargeBO rechargeBO = clearManager.queryClearOrderByPayNo(subPayBO.getTransReceiptNo());

        //银行卡支付-账务充值资金单退款流程
        fundRefundProcess(subPayBO, refundBO, rechargeBO, payToolRefundAmt,
                PayToolTypeEnum.ACCOUNT.getCode(), subRefundBOs);

        //银行卡支付-清分银行卡支付资金单退款流程
        bankRefundProcess(subPayBO, refundBO, rechargeBO, payToolRefundAmt,
                subPayBO.getPayToolType(), subRefundBOs);
    }

    /**
     * 银行卡支付-账务充值退款流程
     *
     * @param subPayBO            原支付单
     * @param refundBO            退款主单
     * @param rechargeBO          充值主单
     * @param payToolRefundAmt    资金单（支付工具）退款金额
     * @param payToolType         支付退款方向
     * @param subRefundBOs        退款资金单
     */
    private void fundRefundProcess(SubPayBO subPayBO, RefundBO refundBO, RechargeBO rechargeBO,
                                   Long payToolRefundAmt, String payToolType, List<SubRefundBO> subRefundBOs){

        List<RechargeFundBO> rechargeFundBOs = rechargeBO.getRechargeFundBO();

        //账务充值退款流程
        SubRefundBO subRefundBO = new SubRefundBO();
        subRefundBO.setFundDetailNo(sequenceFacade.getUniqueSeq());
        subRefundBO.setPayToolType(payToolType);
        subRefundBO.setAmt(payToolRefundAmt);
        subRefundBO.setOrigRequestDate(rechargeBO.getRequestDate());
        subRefundBO.setEventNo(eventProcess(rechargeBO));

        //根据内部资金账户充值单 生成 充值退款转账资金明细
        subRefundBOs.add(
                BO2BOConverter.getSubRefundBOByRechargeFundBO(subPayBO, refundBO, subRefundBO, rechargeFundBOs.get(0)));
    }

    /**
     * 银行卡支付-清分银行卡支付退款流程
     *
     * @param subPayBO            原支付单
     * @param refundBO            退款主单
     * @param rechargeBO          充值主单
     * @param payToolType         支付退款方向
     * @param payToolRefundAmt    本次该资金单（支付工具）退款金额
     * @param subRefundBOs        退款资金单
     */
    private void bankRefundProcess(SubPayBO subPayBO, RefundBO refundBO, RechargeBO rechargeBO,
                                   Long payToolRefundAmt, String payToolType, List<SubRefundBO> subRefundBOs){

        RechargeBankBO rechargeBankBO = rechargeBO.getRechargeBankBO();
        if(null == rechargeBankBO){
            return;
        }
        SubRefundBO subRefundBO = new SubRefundBO();
        subRefundBO.setFundDetailNo(sequenceFacade.getUniqueSeq());
        subRefundBO.setPayToolType(payToolType);
        subRefundBO.setAmt(payToolRefundAmt);
        subRefundBO.setOrigRequestDate(rechargeBO.getRequestDate());

        //根据银行卡充值单 生成 银行卡退款资金明细
        subRefundBOs.add(
                BO2BOConverter.getSubRefundBOByRechargeBankBO(subPayBO, refundBO, subRefundBO, rechargeBankBO));
    }


    /**
     * 退款资金单明细构建
     *
     * @param payToolType         退款主单
     * @param payToolRefundAmt    本次该资金单（支付工具）退款金额
     * @return                    退款资金单明细
     */
    private SubRefundBO subRefundBOBuild(Long payToolRefundAmt, String payToolType){

        SubRefundBO subRefundBO = new SubRefundBO();
        subRefundBO.setFundDetailNo(sequenceFacade.getUniqueSeq());
        subRefundBO.setPayToolType(payToolType);
        subRefundBO.setAmt(payToolRefundAmt);

        return subRefundBO;
    }


    /**
     * 会计事件处理流程
     *
     * <p>
     * 关于银行卡退款流程中，账务充值的退款，考虑支持部分退款，故不直接撤销交易，
     * 而是进行反向转账。其中Dr(借方)账户是原充值的Cr(贷记户)，退款的Cr户通过
     * 会计事件来获取。此处的会计事件与原充值的会计事件对应的内部户是同一个。只
     * 不过处在不同的借贷方向。
     * </p>
     *
     * @param rechargeBO    充值订单
     * @param rechargeBO    会计事件好
     */
    private String eventProcess(RechargeBO rechargeBO){

        //账务充值资金单
        RechargeBankBO rechargeBankBO = rechargeBO.getRechargeBankBO();

        //查询会计事件号
        EventRuleResBO eventRuleResBO = eventService.queryEventNo(EventTypeEnum.CHANNEL_REFUND,
                rechargeBankBO.getSubFundChannel());

        return eventRuleResBO.getEventNo();
    }

}
